#include "../include/Surface.h"
#include "../include/Renderer.h"
#include "../include/Texture.h"
#include "../include/Window.h"
#include <SDL2/SDL.h>
#include <stdexcept>
#include <algorithm>
#include <iostream>

namespace GUL {
    namespace System {
        namespace Graphic {

            Renderer::Renderer() {
                //        this->m_Renderer = SDL_CreateWindowAndRenderer(100,100,0,)
            }

            Renderer::Renderer(Window& win, Uint32 flag) {
                this->m_Renderer = SDL_CreateRenderer(win, -1, flag);
                if (this->m_Renderer == nullptr)
                    throw std::runtime_error("Renderer::Renderer(Window&,Uint32): Couldn't Create Valid Renderer: " + std::string(SDL_GetError()));

                SDL_Color color = {0xdd, 0xbb, 0xca, 0xee};
                this->DrawColor(color);
            }

            Renderer::Renderer(Window& win, int index, Uint32 flag) {
                this->m_Renderer = SDL_CreateRenderer(win, index, flag);
                if (this->m_Renderer == nullptr)
                    throw std::runtime_error("Renderer::Renderer(Window&,int,Uint32): Couldn't Create Valid Renderer: " + std::string(SDL_GetError()));
                SDL_Color color = {0x00, 0, 0, 0x0f};
                this->DrawColor(color);
            }

            Renderer::Renderer(Surface& surf) {
                this->m_Renderer = SDL_CreateSoftwareRenderer(surf);
                if (this->m_Renderer == nullptr)
                    throw std::runtime_error("Renderer::Renderer(Surface&): Couldn't Create Valid SoftwareRenderer: " + std::string(SDL_GetError()));
                SDL_Color color = {0x00, 0, 0, 0x0f};
                this->DrawColor(color);
            }

            Renderer::~Renderer() {
                if (this->m_Renderer != NULL)
                    SDL_DestroyRenderer(this->m_Renderer);
            }

            Texture& Renderer::operator [](const std::string& index) {
                if ((this->m_TextureDictionary.find(index)) == this->m_TextureDictionary.end())
                    throw std::out_of_range("It doesn't exist a Texture with name: '" + index + "' here!");
                return *this->m_TextureDictionary[index];
            }

            void Renderer::insertTexture(const std::string& name, Texture* tex) {
                if (tex == nullptr)
                    throw std::invalid_argument("Renderer::insetTexuter: Texture is a nullptr; nullptr can't be added into the TextureDictionary");
                this->m_TextureDictionary.insert(std::make_pair(const_cast<std::string&> (name), tex));
            }

            SDL_Texture* Renderer::SDLCreateTexture(UPair& Boundary, Uint32 format, int access) {
                return SDL_CreateTexture(*this, format, access, Boundary.first, Boundary.second);
            }

            SDL_Texture* Renderer::SDLCreateTexture(Surface& surf) {
                return SDL_CreateTextureFromSurface(*this, surf);
            }

            SDL_Texture* Renderer::SDLCreateTexture(SDL_Surface* surf) {
                return SDL_CreateTextureFromSurface(*this, surf);
            }

            Texture& Renderer::CreateTexture(const std::string& name, UPair& Boundary, Uint32 format, int access) {
                Texture* tmp = new Texture(*this, Boundary, name, format, access);
                //        this->insertTexture(const_cast<std::string&>(name),tmp);
                return *tmp;
            }

            Texture& Renderer::CreateTexture(const std::string& name, Surface& surf) {
                Texture* tmp = new Texture(*this, surf, name);
                //        this->insertTexture(name,tmp);
                return *tmp;
            }

            Texture& Renderer::CreateTexture(const std::string& name, SDL_Surface* surf) {
                if (surf == nullptr)
                    throw std::invalid_argument("Renderer::CreateTexture: SDL_Surface can't be a nullptr please use a valid SDL_Surface Pointer\n");
                Texture* tmp = new Texture(*this, surf, name);
                //        this->insertTexture(name,tmp);
                return *tmp;
            }

            Texture& Renderer::RenderTarget() {
                return *(new Texture(*this, SDL_GetRenderTarget(*this), "RenderTarget"));
            }
            
            void Renderer::RenderTarget(void*)
            {
                if(SDL_SetRenderTarget(*this,nullptr) ==-1)
                    throw std::runtime_error("Couldn't set Standard Rendertarget. SDL Error: " + std::string(SDL_GetError()));
                std::cerr<<"Set standard Rendertarget\n";
            }

            void Renderer::RenderTarget(Texture& tex) {
                
                if(SDL_SetRenderTarget(*this,nullptr) ==-1)
                    throw std::runtime_error("Couldn't set  Rendertarget. SDL Error: " + std::string(SDL_GetError()));
                std::cerr<<"Set  Rendertarget\n";
//                TexIter tmp = this->m_TextureDictionary.find("RenderTarget");
//
//                if (tmp == this->m_TextureDictionary.end()) {
//                    if (SDL_SetRenderTarget(*this, tex) == -1)
//                        throw std::runtime_error("Couldn't set render target. SDL error: " + std::string(SDL_GetError()) + " !!!");
//                }
//                else
//                    this->m_TextureDictionary.erase(tmp);
//
//                if (SDL_SetRenderTarget(*this, tex) == -1)
//                    throw std::runtime_error("Couldn't set render target. SDL error: " + std::string(SDL_GetError()) + " !!!");
            }

            void Renderer::RenderCopy(Texture& tex, const SDL_Rect* src, const SDL_Rect* dst) {
                if (SDL_RenderCopy(*this, tex, src, dst) == -1)
                    throw std::runtime_error("Couldn't call SDL_RenderCopy correct. SDL_GetError: " + std::string(SDL_GetError()) + "!!!");
            }

            void Renderer::Clear() {
                if (SDL_RenderClear(this->m_Renderer) == -1)
                    throw std::logic_error("Someone do something very stupid and want use the Renderer Clear Memberfunction. " + std::string(SDL_GetError()) + " !!!");
            }

            void Renderer::Present() {
                SDL_RenderPresent(*this);
            }

            void Renderer::LogicalSize(UPair& Boundary) {
                SDL_RenderSetLogicalSize(*this, Boundary.first, Boundary.second);
            }

            UPair& Renderer::LogicalSize() {
                int w = 0, h = 0;
                SDL_RenderGetLogicalSize(*this, &w, &h);
                UPair* tmp = new UPair(std::make_pair(static_cast<unsigned int> (w), static_cast<unsigned int> (h)));
                return *tmp;
            }

            void Renderer::Viewport(const SDL_Rect* rect) {
                SDL_RenderSetViewport(*this, rect);
            }

            SDL_Rect* Renderer::Viewport() {
                SDL_Rect* rect = nullptr;
                SDL_RenderGetViewport(*this, rect);
                return rect;
            }

            void Renderer::DrawColor(SDL_Color col) {
                if (SDL_SetRenderDrawColor(*this, col.r, col.g, col.b, col.a) == -1)
                    throw std::runtime_error("Couldn't set render draw colour!!! " + std::string(SDL_GetError()));
            }

            void Renderer::FillRect(const SDL_Rect* rect) {
                if (SDL_RenderFillRect(*this, rect) == -1)
                    throw std::runtime_error("Couldn't fill a rect with the renderer!!! " + std::string(SDL_GetError()));
            }
            
            void Renderer::DrawPoint(IPair position)
            {
                if(SDL_RenderDrawPoint(this->m_Renderer,position.first,position.second) ==-1)
                    throw std::runtime_error("Couldn't draw a point with the renderer!!! " + std::string(SDL_GetError()));
            }
        }
    }

}